﻿using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Btstack;

namespace TestServer
{
    class Program
    {
        //
        // list service handle ranges
        //
        public const ushort ATT_SERVICE_GAP_SERVICE_START_HANDLE = 0x0001;
        public const ushort ATT_SERVICE_GAP_SERVICE_END_HANDLE = 0x0003;
        public const ushort ATT_SERVICE_GATT_SERVICE_START_HANDLE = 0x0004;
        public const ushort ATT_SERVICE_GATT_SERVICE_END_HANDLE = 0x0006;
        public const ushort ATT_SERVICE_1000_START_HANDLE = 0x0007;
        public const ushort ATT_SERVICE_1000_END_HANDLE = 0x000d;

        //
        // list mapping between characteristics and handles
        //
        public const ushort ATT_CHARACTERISTIC_GAP_DEVICE_NAME_01_VALUE_HANDLE = 0x0003;
        public const ushort ATT_CHARACTERISTIC_GATT_DATABASE_HASH_01_VALUE_HANDLE = 0x0006;
        public const ushort ATT_CHARACTERISTIC_1001_01_VALUE_HANDLE = 0x0009;
        public const ushort ATT_CHARACTERISTIC_1001_01_CLIENT_CONFIGURATION_HANDLE = 0x000a;
        public const ushort ATT_CHARACTERISTIC_1002_01_VALUE_HANDLE = 0x000c;
        public const ushort ATT_CHARACTERISTIC_1002_01_CLIENT_CONFIGURATION_HANDLE = 0x000d;

        public static byte[] ProfileData =
        {
            // ATT DB Version
            1,

            // 0x0001 PRIMARY_SERVICE-GAP_SERVICE
            0x0a, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x28, 0x00, 0x18, 
            // 0x0002 CHARACTERISTIC-GAP_DEVICE_NAME-READ
            0x0d, 0x00, 0x02, 0x00, 0x02, 0x00, 0x03, 0x28, 0x02, 0x03, 0x00, 0x00, 0x2a, 
            // 0x0003 VALUE-GAP_DEVICE_NAME-READ-'LE Streamer'
            // READ_ANYBODY
            0x13, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x2a, 0x4c, 0x45, 0x20, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x65, 0x72, 

            // 0x0004 PRIMARY_SERVICE-GATT_SERVICE
            0x0a, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x28, 0x01, 0x18, 
            // 0x0005 CHARACTERISTIC-GATT_DATABASE_HASH-READ
            0x0d, 0x00, 0x02, 0x00, 0x05, 0x00, 0x03, 0x28, 0x02, 0x06, 0x00, 0x2a, 0x2b, 
            // 0x0006 VALUE-GATT_DATABASE_HASH-READ-''
            // READ_ANYBODY
            0x18, 0x00, 0x02, 0x00, 0x06, 0x00, 0x2a, 0x2b, 0xd9, 0x75, 0x9a, 0x7c, 0xe7, 0x48, 0x5f, 0x08, 0x02, 0x84, 0xd0, 0xf3, 0xc2, 0x8a, 0x0b, 0xca, 
            // Test Service

            // 0x0007 PRIMARY_SERVICE-1000
            0x0a, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00, 0x28, 0x00, 0x10, 
            // Test Characteristic A, write_without_response as well as notify
            // 0x0008 CHARACTERISTIC-1001-WRITE_WITHOUT_RESPONSE | READ | NOTIFY | DYNAMIC
            0x0d, 0x00, 0x02, 0x00, 0x08, 0x00, 0x03, 0x28, 0x16, 0x09, 0x00, 0x01, 0x10, 
            // 0x0009 VALUE-1001-WRITE_WITHOUT_RESPONSE | READ | NOTIFY | DYNAMIC-''
            // READ_ANYBODY, WRITE_ANYBODY
            0x08, 0x00, 0x06, 0x01, 0x09, 0x00, 0x01, 0x10, 
            // 0x000a CLIENT_CHARACTERISTIC_CONFIGURATION
            // READ_ANYBODY, WRITE_ANYBODY
            0x0a, 0x00, 0x0e, 0x01, 0x0a, 0x00, 0x02, 0x29, 0x00, 0x00, 
            // Test Characteristic B, write_without_response as well as notify
            // 0x000b CHARACTERISTIC-1002-WRITE_WITHOUT_RESPONSE | READ | NOTIFY | DYNAMIC
            0x0d, 0x00, 0x02, 0x00, 0x0b, 0x00, 0x03, 0x28, 0x16, 0x0c, 0x00, 0x02, 0x10, 
            // 0x000c VALUE-1002-WRITE_WITHOUT_RESPONSE | READ | NOTIFY | DYNAMIC-''
            // READ_ANYBODY, WRITE_ANYBODY
            0x08, 0x00, 0x06, 0x01, 0x0c, 0x00, 0x02, 0x10, 
            // 0x000d CLIENT_CHARACTERISTIC_CONFIGURATION
            // READ_ANYBODY, WRITE_ANYBODY
            0x0a, 0x00, 0x0e, 0x01, 0x0d, 0x00, 0x02, 0x29, 0x00, 0x00, 

            // END
            0x00, 0x00
        };

        public static int AttWriteCallback(ushort ConHandle, ushort AttributeHandle, ushort TransactionMode, ushort offset, IntPtr BufferPtr, ushort BufferSize)
        {
            byte[] buffer = new byte[BufferSize];
            if (BufferPtr != IntPtr.Zero)
                Marshal.Copy(BufferPtr, buffer, 0, BufferSize);

            switch (AttributeHandle)
            {
                case ATT_CHARACTERISTIC_1001_01_CLIENT_CONFIGURATION_HANDLE:
                case ATT_CHARACTERISTIC_1002_01_CLIENT_CONFIGURATION_HANDLE:
                    var LeNotificationEnabled = BitConverter.ToUInt16(buffer, 0) == Bluetooth.GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION;
                    Console.WriteLine($"Notifications enabled {LeNotificationEnabled}");
                    if (LeNotificationEnabled)
                    {
                        switch (AttributeHandle)
                        {
                            case ATT_CHARACTERISTIC_1001_01_CLIENT_CONFIGURATION_HANDLE:
                                Console.WriteLine($"value_handle: 0x{ATT_CHARACTERISTIC_1001_01_VALUE_HANDLE:x2}");
                                break;
                            case ATT_CHARACTERISTIC_1002_01_CLIENT_CONFIGURATION_HANDLE:
                                Console.WriteLine($"value_handle: 0x{ATT_CHARACTERISTIC_1002_01_VALUE_HANDLE:x2}");
                                break;
                            default:
                                break;
                        }
                        Bluetooth.AttServerRequestCanSendNowEvent(ConHandle);
                    }
                    break;

                case ATT_CHARACTERISTIC_1001_01_VALUE_HANDLE:
                case ATT_CHARACTERISTIC_1002_01_VALUE_HANDLE:
                    Console.WriteLine($"write known: 0x{AttributeHandle:x4}");
                    break;

                default:
                    Console.WriteLine($"Write to 0x{AttributeHandle:x4}, len {buffer.Length}");
                    break;
            }

            return 0;
        }

        static void Main(string[] args)
        {
            var bt = new Bluetooth();

            Console.CancelKeyPress += delegate {
                Console.WriteLine("CTRL-C - SIGINT received, shutting down..");

                bt.PowerOff();

                Console.WriteLine("Exit.");

                Environment.Exit(1);
            };

            bt.OnWorking += () =>
            {
                Console.WriteLine("BTstack up and running at {0}", BitConverter.ToString(bt.LocalBdAddr).Replace("-", ":"));

                Console.WriteLine("Start Advertising...");
                bt.StartAdvertising("btstack", ProfileData, AttWriteCallback);
                Console.WriteLine("Wait connect...");
            };

            bt.OnConnected += async (status, handle, role, interval, latency) =>
            {
                bt.StopAdvertising();

                Console.WriteLine($"Connected: 0x{status:x2} 0x{handle:x4} {role} {interval:F2} {latency}");

                await Task.Delay(1000); // required
                bt.ReadRssi(handle);
            };

            bt.OnDisconnected += (handle, status, reason) =>
            {
                Console.WriteLine($"Disconnected: 0x{handle:x4} 0x{status:x2} 0x{reason:x2}");
            };

            bt.OnRssiMeasurementReport += (handle, rssi) =>
            {
                Console.WriteLine($"RSSI handle: 0x{handle:x4} Rssi: {rssi} db");
            };

            bt.OnPowerOnFailed += () =>
            {
                Console.WriteLine("Power on failed");
            };

            bt.PowerOn();

            bt.StartRunLoop();
        }
    }
}
